import time
import os
import datetime
from appium import webdriver
from openpyxl import load_workbook
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import jinja2
import matplotlib.pyplot as plt
from appium.webdriver.common.mobileby import MobileBy

plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 如果不存在failshot文件夹，则创建
if not os.path.exists('../app-failshot'):
    os.makedirs('../app-failshot')


# 定义一个函数，用于读取Excel中的测试数据
def read_excel_data(file_path):
    data_dict = {}
    workbook = load_workbook(file_path)
    sheet_names = workbook.sheetnames
    for sheet_index, sheet_name in enumerate(sheet_names):
        # print(f"测试工作表：{sheet_name}")
        sheet_data = {}
        sheet = workbook[sheet_name]
        for row in sheet.iter_rows(min_row=2, values_only=True):
            while row and row[-1] is None:
                row = row[:-1]
            if len(row) != 10:
                continue
            test_case_id, test_point, script_number, test_step, is_execute, step_keyword, locate_method, locate_address, param_value, expected_result = row
            if test_case_id is None:
                continue
            if test_case_id not in sheet_data:
                sheet_data[test_case_id] = []
            # print(f"数据行：{test_point}, {script_number}, {test_step}, {is_execute}, {step_keyword}, {locate_method}, {locate_address}, param_value={param_value}, expected_result={expected_result}")
            sheet_data[test_case_id].append(
                (test_case_id, test_point, script_number, test_step, is_execute, step_keyword, locate_method,
                 locate_address, param_value, expected_result))
        data_dict[sheet_name] = sheet_data
    return data_dict


# 定义Appium的期望能力配置（Desired Capabilities），这里示例配置，你需要根据实际情况修改
def get_desired_caps():
    desired_caps = {
        "platformName": "Android",
        "platformVersion": "9",
        "appPackage": "com.seven.qaml.burstserven",
        "appActivity": "com.seven.qaml.burstserven.ui.LoginActivity",
        "deviceName": "2023110100001087",  # 替换为实际设备名称，可通过 `adb devices`查看
        "automationName": "Uiautomator2",
        "noReset": True
    }
    return desired_caps


# 启动Appium驱动，连接到安卓设备
def start_driver():
    desired_caps = get_desired_caps()
    driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
    return driver


# 根据定位方式和定位地址查找元素，并添加元素等待逻辑
def find_element(driver, locate_method, locate_address):
    wait = WebDriverWait(driver, 10)  # 设置最长等待时间为10秒，可根据实际情况调整
    if locate_method == "id":
        return wait.until(EC.presence_of_element_located((By.ID, locate_address)))
    elif locate_method == "xpath":
        return wait.until(EC.presence_of_element_located((By.XPATH, locate_address)))
    elif locate_method == "class_name":
        return wait.until(EC.presence_of_element_located((By.CLASS_NAME, locate_address)))
    elif locate_method == "android_uiautomator":
        return wait.until(EC.presence_of_element_located((MobileBy.ANDROID_UIAUTOMATOR, locate_address)))
    elif locate_method == "accessibility_id":
        return wait.until(EC.presence_of_element_located((MobileBy.ACCESSIBILITY_ID, locate_address)))
    elif locate_method == "image":
        return wait.until(EC.presence_of_element_located((MobileBy.IMAGE, locate_address)))
    elif locate_method == "content-desc":
        return wait.until(EC.presence_of_element_located((MobileBy.CONTENT_DESC, locate_address)))
    elif locate_method == "text":
        return wait.until(EC.presence_of_element_located((MobileBy.TEXT, locate_address)))
    elif locate_method == "resource-id":
        return wait.until(EC.presence_of_element_located((MobileBy.RESOURCE_ID, locate_address)))
    elif locate_method == "viewtag":
        return wait.until(EC.presence_of_element_located((MobileBy.VIEWTAG, locate_address)))
    elif locate_method == "custom":
        return wait.until(EC.presence_of_element_located((MobileBy.CUSTOM, locate_address)))
    # 以下是基于 Appium 特定版本支持的一些其他定位方式示例，根据实际 Appium 版本情况选用
    elif locate_method == "predicate":
        return wait.until(EC.presence_of_element_located((MobileBy.PREDICATE, locate_address)))
    elif locate_method == "class-chain":
        return wait.until(EC.presence_of_element_located((MobileBy.CLASS_CHAIN, locate_address)))
    else:
        raise ValueError(f"不支持的定位方式: {locate_method}")


# 执行点击操作的方法
def click_element(element):
    element.click()
    # print(f"已成功执行步骤：点击操作")


# 执行输入文本操作的方法
def send_text_to_element(element, text):
    try:
        element.send_keys(text)
        # print(f"已成功执行步骤：输入 {text}")
    except Exception as e:
        print(f"输入 {text}失败")
        return False, e


# 等待方法，根据传入的秒数进行等待
def wait_for_seconds(driver, seconds):
    try:
        time.sleep(int(seconds))
        # print(f"已成功等待 {seconds} 秒")
    except Exception as e:
        print(f"等待 {seconds} 秒过程中出现问题")
        return False, e


# 比较元素相同文本的方法
def compare_same_text(driver, locate_method, locate_address, expected_text):
    try:
        element = find_element(driver, locate_method, locate_address)
        actual_text = element.text
        if actual_text == expected_text:
            print(f"元素文本比较相同，预期文本：{expected_text}，实际：{actual_text}，用例通过")
            return True
        else:
            print(f"元素文本比较不同，预期文本：{expected_text}，实际：{actual_text}，用例不通过")
            return False
    except Exception as e:
        print(f"比较元素文本时出现问题，定位方式: {locate_method}，定位地址: {locate_address}")
        return False, e


# 比较元素不同文本的方法
def compare_different_text(driver, locate_method, locate_address, unexpected_text):
    try:
        element = find_element(driver, locate_method, locate_address)
        actual_text = element.text
        if actual_text != unexpected_text:
            print(f"元素文本比较不同，预期文本不应为：{unexpected_text}，实际：{actual_text}，用例通过")
            return True
        else:
            print(f"元素文本比较相同，预期文本不应为：{unexpected_text}，实际：{actual_text}，用例不通过")
            return False
    except Exception as e:
        print(f"比较元素文本时出现问题，定位方式: {locate_method}，定位地址: {locate_address}")
        return False, e


# 判断是否出现toast提示的方法
def check_toast_present(driver, expected_present):
    try:
        toast_locator = ("xpath", "//*[contains(@text,'')]")  # 通用的toast定位方式，根据实际情况可能需调整
        wait = WebDriverWait(driver, 5)  # 适当设置等待时间，可根据实际调整
        if expected_present.lower() == "y":
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located(("xpath", '//android.widget.Toast')))
            print("检测到toast提示出现")
            return True
        elif expected_present.lower() == "n":
            WebDriverWait(driver, 10).until_not(
                EC.presence_of_element_located(("xpath", '//android.widget.Toast')))
            print("未检测到toast提示出现")
            return True
        else:
            print("参数值填写错误，请在参数值一栏填写y或者n")
            return False
    except Exception as e:
        print("判断toast提示出现情况时出现问题")
        return False, e


# 判断toast提示文本是否和指定文本相同的方法
def check_toast_text_same(driver, expected_text):
    try:
        toast = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located(("xpath", '//android.widget.Toast')))
        # 获取 Toast 的文本内容
        actual_text = toast.text
        if actual_text == expected_text:
            print(f"toast提示文本与预期文本相同，预期文本：{expected_text}，实际文本：{actual_text}，用例通过")
            return True
        else:
            print(f"toast提示文本与预期文本不同，预期文本：{expected_text}，实际文本：{actual_text}，用例不通过")
            return False
    except Exception as e:
        print(f"判断toast提示文本是否和指定文本 {expected_text} 相同时出现问题")
        return False, e


# 判断toast提示文本是否和指定文本不相同的方法
def check_toast_text_dif(driver, unexpected_text):
    try:
        toast = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located(("xpath", '//android.widget.Toast')))
        # 获取 Toast 的文本内容
        actual_text = toast.text
        if actual_text != unexpected_text:
            print(f"toast提示文本与预期不应出现的文本不同，预期不应出现文本：{unexpected_text}，实际_text：{actual_text}，用例通过")
            return True
        else:
            print(f"toast提示文本与预期不应出现的文本相同，预期不应出现文本：{unexpected_text}，实际_text：{actual_text}，用例不通过")
            return False
    except Exception as e:
        print(f"判断toast提示文本是否和指定文本 {unexpected_text} 不相同时出现问题")
        return False, e


# 执行单个测试用例的操作，确保按照脚本编号顺序执行步骤，并准确根据是否执行设置来处理步骤
def execute_test_case(excel_data, driver):
    total_cases = 0
    passed_cases = 0
    failed_cases = 0
    case_results = []
    for sheet_name, sheet_data in excel_data.items():
        print(f"开始执行工作表 {sheet_name} 中的测试用例")
        sheet_total_cases = len(sheet_data)
        sheet_passed_cases = 0
        sheet_failed_cases = 0
        sheet_case_results = []
        for test_case_id in sorted(sheet_data.keys()):
            test_case_data = sheet_data[test_case_id]
            sorted_data = sorted(test_case_data, key=lambda x: x[1])
            case_result = True
            steps = []
            for data in sorted_data:
                test_case_id, test_point, script_number, test_step, is_execute, step_keyword, locate_method, locate_address, param_value, expected_result = data
                if is_execute is not None and is_execute.lower() == 'y':
                    try:
                        # 只执行属于当前工作表的步骤
                        if step_keyword == "sendstr":
                            element = find_element(driver, locate_method, locate_address)
                            send_text_to_element(element, param_value)
                            steps.append(f"输入 {param_value} 到 {locate_address}")
                        elif step_keyword == "click":
                            element = find_element(driver, locate_method, locate_address)
                            click_element(element)
                            steps.append(f"点击 {locate_address}")
                        elif step_keyword == "wait":
                            wait_for_seconds(driver, param_value)
                            steps.append(f"等待 {param_value} 秒")
                        elif step_keyword == "compare_same":
                            result = compare_same_text(driver, locate_method, locate_address, param_value)
                            if not result:
                                case_result = False
                            steps.append(f"比较文本是否为 {param_value}，结果：{result}")
                        elif step_keyword == "compare_dif":
                            result = compare_different_text(driver, locate_method, locate_address, param_value)
                            if not result:
                                case_result = False, result
                            steps.append(f"比较文本是否不为 {param_value}，结果：{result}")
                        elif step_keyword == "toast":
                            result = check_toast_present(driver, param_value)
                            if not result:
                                case_result = False, result
                            steps.append(f"判断toast提示是否出现，预期出现情况：{param_value}，结果：{result}")
                        elif step_keyword == "toast_text_same":
                            result = check_toast_text_same(driver, param_value)
                            if not result:
                                case_result = False, result
                            steps.append(f"判断toast提示文本是否与预期文本相同，预期文本：{param_value}，结果：{result}")
                        elif step_keyword == "toast_text_dif":
                            result = check_toast_text_dif(driver, param_value)
                            if not result:
                                case_result = False, result
                            steps.append(f"判断toast提示文本是否与预期不应出现的文本不同，预期不应出现文本：{param_value}，结果：{result}")
                        # 简单的结果验证逻辑，如果操作没有抛出异常，认为达到预期结果“成功”
                        if expected_result == "成功":
                            print(f"\033[92m{test_case_id}-{script_number}-{test_step}：{expected_result}\033[0m")
                    except:
                        print(
                            f"在执行测试用例 {test_case_id} 的步骤 {step_keyword} 时出现问题，定位方式: {locate_method}, 定位地址: {locate_address}")
                        case_result = False
                        steps.append(f"执行步骤 {step_keyword} 失败")
                        break
            if case_result:
                sheet_passed_cases += 1
            else:
                sheet_failed_cases += 1
                screenshot_path = f"app-failshot/{test_case_id}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.png"
                driver.save_screenshot(screenshot_path)
                print(f"\033[91m测试用例 {test_case_id}-{script_number} 执行失败，已截图保存至 {screenshot_path}\033[0m")
            sheet_case_results.append({
                "测试用例编号": test_case_id,
                "执行结果": "通过" if case_result else "失败",
                "步骤": steps
            })

        print(f"工作表 {sheet_name} 中的测试用例执行完成")
        print(f"共执行 {sheet_total_cases} 个测试用例，通过 {sheet_passed_cases} 个，失败 {sheet_failed_cases} 个")

        # 重新启动app到登录界面
        desired_caps = get_desired_caps()
        driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

        total_cases += sheet_total_cases
        passed_cases += sheet_passed_cases
        failed_cases += sheet_failed_cases
        case_results.extend(sheet_case_results)

    print(f"所有工作表中的用例已测试完成")
    print(f"所有表单中共执行 {total_cases} 个测试用例，通过 {passed_cases} 个，失败 {failed_cases} 个")
    # 生成美化后的HTML测试报告
    generate_test_report(total_cases, passed_cases, failed_cases, case_results)


def generate_test_report(total_cases, passed_cases, failed_cases, case_results):
    # 生成饼图
    labels = '通过', '失败'
    sizes = [passed_cases, failed_cases]
    explode = (0, 0.1)  # 突出显示失败部分
    fig1, ax1 = plt.subplots()
    ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
            shadow=True, startangle=90)
    ax1.axis('equal')  # 保证饼图是圆形
    plt.savefig('test_report_chart.png')  # 保存饼图为图片

    # 使用Jinja2模板生成HTML报告，指定utf-8编码读取文件
    with open('test_report_template.html', encoding='utf-8') as file:
        template = jinja2.Template(file.read())

    report_html = template.render(
        total_cases=total_cases,
        passed_cases=passed_cases,
        failed_caps=failed_cases,
        case_results=case_results
    )

    with open('test_report.html', 'w', encoding='utf-8') as f:
        f.write(report_html)
    print("测试报告已生成，路径为：test_report.html")


if __name__ == "__main__":
    # 读取Excel数据文件，假设文件名为test_data.xlsx，和代码在同一目录下，可根据实际调整路径
    excel_data = read_excel_data("test.xlsx")
    # 最后再执行一次总的测试，包含所有工作表的数据
    driver = start_driver()
    execute_test_case(excel_data, driver)
    print("所有工作表已测试完成")
